package com.soonoh.simplecube.cube;

public class BigCube {

	public static final int SIZE = 3;
	public static final int BIG_CUBE_LAST_INDEX = SIZE - 1;
	static final int ROTATE_CUBE_COUNT = SIZE * SIZE;

    /**
     * the index of cube to cash currently, when we rotate cubes in one face, each cubes to be gonna rotate
     * is rotated respectably and saved in cash first. after finished all rotation of each cubes, finally propagate
     * cubes rotates states to cubes in BigCube.
     */
    int currentCashIndex;

    /** each cubes in this BigCube. */
    Cube[][][] BigCubeChildren = new Cube[SIZE][SIZE][SIZE];

    /** a cash cubes to be rotated. */
    CashCube[] BigCubeCashCube = new CashCube[ROTATE_CUBE_COUNT];

	public BigCube() {

        currentCashIndex = 0;
        int x, y, z;
        for( x = 0; x < SIZE; ++x ) {
            for( y = 0; y < SIZE; ++y ) {
                for( z = 0; z < SIZE; ++z ) {
                    if ( (x > 0 && x < BIG_CUBE_LAST_INDEX) && (y > 0 && y < BIG_CUBE_LAST_INDEX) && (z > 0 && z < BIG_CUBE_LAST_INDEX) ) {
                        // core cube can be removed.
                        BigCubeChildren[x][y][z] = null;
                        continue;
                    }
                    Cube cube = new Cube();
                    BigCubeChildren[x][y][z] = cube;

                    // now each face to proper colour(to set groups or to set faces)
                    if (x == 0) {
                        cube.setColour(Cube.LEFT, Cube.LEFT);
                    }
                    if (x == BIG_CUBE_LAST_INDEX) {
                        cube.setColour(Cube.RIGHT, Cube.RIGHT);
                    }
                    if (y == 0) {
                        cube.setColour(Cube.BOTTOM, Cube.BOTTOM);
                    }
                    if (y == BIG_CUBE_LAST_INDEX) {
                        cube.setColour(Cube.TOP, Cube.TOP);
                    }
                    if (z == 0) {
                        cube.setColour(Cube.BACK, Cube.BACK);
                    }
                    if (z == BIG_CUBE_LAST_INDEX) {
                        cube.setColour(Cube.FRONT, Cube.FRONT);
                    }
                }
            }
        }
        for( x = 0; x < ROTATE_CUBE_COUNT; ++ x ) {
            BigCubeCashCube[x] = new CashCube();
        }
	}
    /**
     * propagate cubes states to real cubes which is rotated.
     */
    void syncCube() {
        int i;
        int x, y, z;
        for( i = 0; i < currentCashIndex; ++i ) {
            x = BigCubeCashCube[i].getX();
            y = BigCubeCashCube[i].getY();
            z = BigCubeCashCube[i].getZ();
            Cube cube = BigCubeChildren[x][y][z];
            if (cube != null) {
                cube.setColours( BigCubeCashCube[i].getColours() );
            }
        }
        currentCashIndex = 0;
    }
    
    public void rotate(CubeRotation rotation) {
    	rotate(rotation.getAxis(), rotation.getColumn(), rotation.getDirection());
    }

	protected void rotate(int axis, int column, int direction) {
		int columns[] = new int[3];
		int a = -1 , b = -1;
		switch (axis) {
			case CubeRotation.AXIS_X:
				a = CubeRotation.AXIS_Y; b = CubeRotation.AXIS_Z;
				columns[CubeRotation.AXIS_X] = column;
				break;
			case CubeRotation.AXIS_Y:
				a = CubeRotation.AXIS_Z; b = CubeRotation.AXIS_X;
				columns[CubeRotation.AXIS_Y] = column;
				break;
			case CubeRotation.AXIS_Z:
				a = CubeRotation.AXIS_X; b = CubeRotation.AXIS_Y;
				columns[CubeRotation.AXIS_Z] = column;
				break;
		}

        for( columns[a] = 0; columns[a] < SIZE; ++columns[a] ) {
            for( columns[b] = 0; columns[b] < SIZE; ++columns[b]  ) {

				int newColumns[] = new int[3];
				newColumns[CubeRotation.AXIS_X] = columns[CubeRotation.AXIS_X];
				newColumns[CubeRotation.AXIS_Y] = columns[CubeRotation.AXIS_Y];
				newColumns[CubeRotation.AXIS_Z] = columns[CubeRotation.AXIS_Z];

				switch (direction) {
				case CubeRotation.ROTATION_ANTICLOCKWISE: 
					newColumns[a] = BIG_CUBE_LAST_INDEX - columns[b];
					newColumns[b] = columns[a];
					break;
				case CubeRotation.ROTATION_OPPOSITE: 
					newColumns[a] = BIG_CUBE_LAST_INDEX - columns[a];
					newColumns[b] = BIG_CUBE_LAST_INDEX - columns[b];
					break;
				case CubeRotation.ROTATION_CLOCKWISE: 
					newColumns[a] = columns[b];
					newColumns[b] = BIG_CUBE_LAST_INDEX - columns[a];
					break;
				}

				// each cube rotates and set rotated colours.
				int x, y, z, rx, ry, rz;
				x = columns[CubeRotation.AXIS_X];
				y = columns[CubeRotation.AXIS_Y];
				z = columns[CubeRotation.AXIS_Z];
				rx = newColumns[CubeRotation.AXIS_X];
				ry = newColumns[CubeRotation.AXIS_Y];
				rz = newColumns[CubeRotation.AXIS_Z];
				if (this.BigCubeChildren[x][y][z] != null)
				{
					this.BigCubeChildren[x][y][z].rotate( this.BigCubeCashCube[currentCashIndex].getColours(), axis, direction );
					this.setCashCube( rx, ry, rz );
				}
			}
        }
        this.syncCube();
	}

    /**
     * rotate cubes based on X axis in this BigCube.
     */
    public void rotateX( int x, int rotateTimes ) {
		this.rotate(CubeRotation.AXIS_X, x, rotateTimes);
    }

    /**
     * rotate cubes based on Y axis in this BigCube.
     */
    public void rotateY( int y, int rotateTimes ) {
		this.rotate(CubeRotation.AXIS_Y, y, rotateTimes);
    }

    /**
     * rotate cubes based on Z axis in this BigCube.
     */
    public void rotateZ( int z, int rotateTimes ) {
		this.rotate(CubeRotation.AXIS_Z, z, rotateTimes);
    }
    /**
     * create cash cube located in x,y,z.
     */
    void setCashCube( int x, int y, int z ) {
        this.BigCubeCashCube[currentCashIndex].initCashCube( x, y, z );
        ++currentCashIndex;
        if( currentCashIndex > ROTATE_CUBE_COUNT ) {
            this.syncCube();
        }
    }

    void setCube( Cube cube, int x, int y, int z ) {

        this.BigCubeChildren[ x ][ y ][ z ] = cube;

    }

    public Cube getCube( int x, int y, int z ) {
        return this.BigCubeChildren[ x ][ y ][ z ];
    }

    public boolean isPerfectCube() {
        int x, y, z;
        int left, right, top, bottom, front, back;
        left = right = top = bottom = front = back = Cube.HIDDEN;
        for( x = 0; x < SIZE; ++x ) {
            for( y = 0; y < SIZE; ++y ) {
                for( z = 0; z < SIZE; ++z ) {
                    Cube cube = BigCubeChildren[x][y][z];
                    if (cube == null) continue;

                    // now each face to proper colour(to set groups or to set faces)
                    if (x == 0) {
                        if (left == Cube.HIDDEN) {
                            left = cube.getColour(Cube.LEFT);
                        } else {
                            if (cube.getColour(Cube.LEFT) != left) return false;
                        }
                    }
                    if (x == BIG_CUBE_LAST_INDEX) {
                        if (right == Cube.HIDDEN) {
                            right = cube.getColour(Cube.RIGHT);
                        } else {
                            if (cube.getColour(Cube.RIGHT) != right) return false;
                        }
                    }
                    if (y == 0) {
                        if (bottom == Cube.HIDDEN) {
                            bottom = cube.getColour(Cube.BOTTOM);
                        } else {
                            if (cube.getColour(Cube.BOTTOM) != bottom) return false;
                        }
                    }
                    if (y == BIG_CUBE_LAST_INDEX) {
                        if (top == Cube.HIDDEN) {
                            top = cube.getColour(Cube.TOP);
                        } else {
                            if (cube.getColour(Cube.TOP) != top) return false;
                        }
                    }
                    if (z == 0) {
                        if (back == Cube.HIDDEN) {
                            back = cube.getColour(Cube.BACK);
                        } else {
                            if (cube.getColour(Cube.BACK) != back) return false;
                        }
                    }
                    if (z == BIG_CUBE_LAST_INDEX) {
                        if (front == Cube.HIDDEN) {
                            front = cube.getColour(Cube.FRONT);
                        } else {
                            if (cube.getColour(Cube.FRONT) != front) return false;
                        }
                    }
                }
            }
        }
        return true;
    }
}

class CashCube {
	private int x, y, z;
	private int[] colours;
	
	public CashCube() {
		initCashCube(0, 0, 0);
        colours = new int[Cube.CUBE_FACE_SIZE];
	}

	public int getX() {
		return x;
	}
	public int getY() {
		return y;
	}
	public int getZ() {
		return z;
	}
	public int[] getColours() {
		return colours;
	}

    void initCashCube( int x, int y, int z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }
}
